import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef } from 'react'

import { useDataUpdates, useNodesContext, useTreeApi } from '../context'
import { useDragHook } from '../dnd/drag-hook'
import { useDropHook } from '../dnd/drop-hook'
import { useFreshNode } from '../hooks/use-fresh-node'

type Props = {
  style: React.CSSProperties
  index: number
}

export const RowContainer = React.memo(function RowContainer<T>({ index, style }: Props) {
  /* When will the <Row> will re-render.
   *
   * The row component is memo'd so it will only render
   * when a new instance of the NodeApi class is passed
   * to it.
   *
   * The TreeApi instance is stable. It does not
   * change when the internal state changes.
   *
   * The TreeApi has all the references to the nodes.
   * We need to clone the nodes when their state
   * changes. The node class contains no state itself,
   * It always checks the tree for state. The tree's
   * state will always be up to date.
   */

  useDataUpdates() // Re-render when tree props or visability changes
  const _ = useNodesContext() // So that we re-render appropriately
  const tree = useTreeApi<T>() // Tree already has the fresh state
  const node = useFreshNode<T>(index)

  const el = useRef<HTMLDivElement | null>(null)
  const dragRef = useDragHook<T>(node)
  const dropRef = useDropHook(el, node)
  const innerRef = useCallback(
    (n: HTMLDivElement | null) => {
      el.current = n
      dropRef(n)
    },
    [dropRef]
  )

  const indent = tree.indent * node.level
  const nodeStyle = useMemo(() => ({ paddingLeft: indent }), [indent])
  const rowStyle = useMemo(
    () => ({
      ...style,
      top:
        parseFloat(style.top as string) +
        (tree.props.padding ?? tree.props.paddingTop ?? 0),
    }),
    [style, tree.props.padding, tree.props.paddingTop]
  )

  const rowAttrs: React.HTMLAttributes<unknown> = {
    role: 'treeitem',
    'aria-level': node.level,
    'aria-selected': node.isSelected,
    style: rowStyle,
    tabIndex: -1,
    className: tree.props.rowClassName,
  }

  useEffect(() => {
    if (!node.isEditing && node.isFocused) {
      el.current?.focus({ preventScroll: true })
    }
  }, [node.isEditing, node.isFocused, el.current])

  const Node = tree.renderNode
  const Row = tree.renderRow

  return (
    <Row node={node} innerRef={innerRef} attrs={rowAttrs} key={node.id}>
      <Node node={node} tree={tree} style={nodeStyle} dragHandle={dragRef} />
    </Row>
  )
})
